package com.schoolvisitors.service;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.schoolvisitors.dto.*;
import com.schoolvisitors.entity.*;
import com.schoolvisitors.enums.*;
import com.schoolvisitors.exception.BusinessException;
import com.schoolvisitors.repository.*;
import com.schoolvisitors.response.ResultCode;
import com.schoolvisitors.vo.TimeSlotVo;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

@Slf4j
@Service
public class InstrumentService {

    private final InstrumentRepository instrumentRepository;

    private final RoomService roomService;

    private final InstrumentSubTypeRepository instrumentSubTypeRepository;
    private final TimeSlotRepository timeSlotRepository;
    private final UserService userService;
    private final InstrumentLoanRepository instrumentLoanRepository;
    private final ReservationRepository reservationRepository;
    private final AdminRepository adminRepository;
    private final AdminPermissionService adminPermissionService;
    private final TimeSlotService timeSlotService;

    public InstrumentService(InstrumentRepository instrumentRepository, RoomService roomService, InstrumentSubTypeRepository instrumentSubTypeRepository, TimeSlotRepository timeSlotRepository, UserService userService, InstrumentLoanRepository instrumentLoanRepository, ReservationRepository reservationRepository, AdminRepository adminRepository, AdminPermissionService adminPermissionService, TimeSlotService timeSlotService) {
        this.instrumentRepository = instrumentRepository;
        this.roomService = roomService;
        this.timeSlotService = timeSlotService;

        this.instrumentSubTypeRepository = instrumentSubTypeRepository;
        this.timeSlotRepository = timeSlotRepository;
        this.userService = userService;
        this.instrumentLoanRepository = instrumentLoanRepository;
        this.reservationRepository = reservationRepository;
        this.adminRepository = adminRepository;
        this.adminPermissionService = adminPermissionService;
    }

    //
    public void createInstrument(CreateInstrumentDto dto) {
        Room room = roomService.getRoom(dto.getRoomId());
        if (room == null) {
            throw new BusinessException(ResultCode.ROOM_NOT_FOUND);
        }
        Instrument instrument = Instrument.createDtoToEntity(dto);
        instrumentRepository.save(instrument);
    }

    public Page<Instrument> getInstruments(Pageable pageable) {
        return instrumentRepository.findAllByDeleteFalse(pageable);
    }
    public List<Instrument> getInstruments() {
        return instrumentRepository.findAllByDeleteFalse();
    }

    public JSONObject getInstrumentsByGroupJson() {
        List<Instrument> instruments = getInstruments();
        List<InstrumentSubType> instrumentSubTypes = instrumentSubTypeRepository.findAll();


        // 一级类型 -> List<二级分类JSONObject>
        JSONObject resultJson = new JSONObject();

        // 结构示意：
        // Map<String, List<JSONObject>> 一级类型 -> 二级分类数组
        Map<String, List<JSONObject>> tempMap = new HashMap<>();

        for (Instrument instrument : instruments) {
            String typeName = InstrumentType.getNameByCode(instrument.getType());
            if (typeName == null) {
                typeName = "未知一级类型";
            }
            String subTypeName = instrument.getSubType().getName();

            // 从一级类型对应的二级列表中找有没有subTypeName对应的对象
            List<JSONObject> subTypeList = tempMap.computeIfAbsent(typeName, k -> new ArrayList<>());

            // 查找二级分类对象
            JSONObject subTypeObj = null;
            for (JSONObject obj : subTypeList) {
                if (subTypeName.equals(obj.getString("name"))) {
                    subTypeObj = obj;
                    break;
                }
            }

            // 如果没找到，新建
            if (subTypeObj == null) {
                subTypeObj = new JSONObject();
                subTypeObj.put("name", subTypeName);
                subTypeObj.put("instruments", new JSONArray());
                subTypeList.add(subTypeObj);
            }

            // 把仪器加入 instruments 数组
            JSONArray instrumentArray = subTypeObj.getJSONArray("instruments");
            instrumentArray.add(instrument);
        }

        // 把Map转成JSONObject返回
        resultJson.putAll(tempMap);

        return resultJson;
    }

    public Instrument getInstrumentById(Long instrumentId) {
        return instrumentRepository.findByIdAndDeleteFalse(instrumentId).orElseThrow(() -> new BusinessException(ResultCode.INSTRUMENT_NOT_FOUND));
    }

    public JSONArray getTimeSlotsVo(Long userId, Long instrumentId, LocalDate date) {
        Instrument instrument = getInstrumentById(instrumentId);

        List<TimeSlot> timeSlots = timeSlotRepository.findAllByDeleteFalseOrderByStartTimeAsc();
        log.info("获取仪器: {} 时间 {} 的时段列表", instrumentId, date);
        List<Reservation> reservations = reservationRepository.findByTypeAndInstrumentIdAndDate(ReservationTypeEnum.INSTRUMENT, instrumentId, date);

        return TimeSlotVo.reservationToVos(userId, timeSlots, reservations);
    }

    // 预约仪器
    @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class)
    public void reservationInstrument(User user, @Valid ReservationInstrumentDto dto) {
        // 检查仪器是否存在
        Instrument instrument = instrumentRepository.findByIdForLock(dto.getInstrumentId())
                .orElseThrow(() -> new BusinessException(ResultCode.INSTRUMENT_NOT_FOUND));
        log.info("获取锁成功");

        log.info("用户 {} 预约仪器 {}", user.getId(), dto.getInstrumentId());
        if (!user.getReservationPermission()) {
            log.info("用户无预约权限");
            throw new BusinessException(ResultCode.USER_NOT_RESERVATION_PERMISSION);
        }

        // 检查时间段是否存在
        TimeSlot timeSlot = timeSlotService.getTimeSlotById(dto.getTimeSlotId());
        if (timeSlot == null) {
            throw new BusinessException(ResultCode.TIME_SLOT_NOT_FOUND);
        }

        // 判断是否预约过去的时间段

        LocalDate today = LocalDate.now();
        if (dto.getDate().isBefore(today)) {
            throw new BusinessException(ResultCode.TIME_SLOT_EXPIRED);
        } else if (dto.getDate().isEqual(today)) {
            LocalTime now = LocalTime.now();
            if (timeSlot.getEndTime().isBefore(now)) {
                throw new BusinessException(ResultCode.TIME_SLOT_EXPIRED);
            }
        }

        // 检查是否已经被预约
        Optional<Reservation> existing = reservationRepository
                .findByTypeAndInstrumentIdAndDateAndTimeSlotIdAndStatus(
                        ReservationTypeEnum.INSTRUMENT,
                        dto.getInstrumentId(),
                        dto.getDate(),
                        dto.getTimeSlotId(),
                        ReservationStatusEnum.APPROVED
                );

        if (existing.isPresent()) {
            throw new BusinessException(ResultCode.INSTRUMENT_IS_RESERVED);
        }

        // 检查当前用户是否有相同预约申请，且状态为审核中
        Optional<Reservation> pendingRequest = reservationRepository
                .findByTypeAndInstrumentIdAndDateAndTimeSlotIdAndUserIdAndStatus(
                        ReservationTypeEnum.INSTRUMENT,
                        dto.getInstrumentId(),
                        dto.getDate(),
                        dto.getTimeSlotId(),
                        user.getId(),
                        ReservationStatusEnum.PENDING
                );

        if (pendingRequest.isPresent()) {
            throw new BusinessException(ResultCode.RESERVATION_IS_PENDING);
        }

//        // 检查审核人是否存在
//        User reviewer = userRepository .findById(dto.getReviewerId())
//                .orElseThrow(() -> new BusinessException(ResultCode.REVIEWER_NOT_FOUND));

        // 检查积分
        Long cost = instrument.getPointsCost();
        if (user.getPoints() < cost) {
            throw new BusinessException(ResultCode.USER_INSUFFICIENT_POINTS);
        }
        log.info("用户积分: {}, 预约所需积分: {}", user.getPoints(), cost);

        userService.subtractPoint(user.getId(), cost);
        log.info("扣除后积分: {}", user.getPoints());
        Reservation reservation = new Reservation();
        reservation.setType(ReservationTypeEnum.INSTRUMENT);
        reservation.setUser(user);
        reservation.setInstrument(instrument);
        reservation.setTimeSlot(timeSlot);
        reservation.setReviewerId(dto.getReviewerId());
        reservation.setDate(dto.getDate());
        reservation.setPoints(cost);
        reservation.setStatus(ReservationStatusEnum.PENDING);
        reservation.setCreateAt(LocalDateTime.now());
        reservation.setUpdateAt(LocalDateTime.now());

        reservationRepository.save(reservation);

    }

    public void updateInstrument(CreateInstrumentDto instrumentDto) {
        if (instrumentDto.getId() == null) {
            throw new BusinessException(ResultCode.INSTRUMENT_NOT_FOUND);
        }
        Room room = roomService.getRoom(instrumentDto.getRoomId());//
        if (room == null) {
            throw new BusinessException(ResultCode.ROOM_NOT_FOUND);
        }

        // .orElseThrow(() -> new BusinessException(ResultCode.ROOM_NOT_FOUND));
        Instrument instrument = instrumentRepository.findByIdAndDeleteFalse(instrumentDto.getId()).orElseThrow(() -> new BusinessException(ResultCode.INSTRUMENT_NOT_FOUND));
        instrument.setName(instrumentDto.getName());
        instrument.setType(instrumentDto.getType());
        InstrumentSubType instrumentSubType = new InstrumentSubType();
        instrumentSubType.setId(instrumentDto.getSubType());
        instrument.setSubType(instrumentSubType);
        instrument.setRoom(room);
        instrument.setAvailable(instrumentDto.getAvailable());
        instrument.setPointsCost(instrumentDto.getPointsCost());
        instrument.setImgName(instrumentDto.getImgName());
        instrumentRepository.save(instrument);

    }

    // 借用列表中的仪器
    @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class)
    public void borrowInstrumentFromList(User user, BorrowInstrumentFromListDto dto) {
        log.info("{}", dto.toString());
        Instrument instrument = instrumentRepository.findByIdForLock(dto.getInstrumentId()).orElseThrow(() -> new BusinessException(ResultCode.INSTRUMENT_NOT_FOUND));
        // 未开放预约
        if (!instrument.getAvailable()){
            throw new BusinessException(ResultCode.NOT_AVAILABLE);
        }

        // 验证审核人权限
        Long adminId = dto.getReviewerId();
        Admin admin = adminRepository.findById(adminId).orElseThrow(() -> new BusinessException(ResultCode.REVIEWER_NOT_FOUND));
        if (!adminPermissionService.hasInstrumentPermission(admin, instrument)){
            throw new BusinessException(ResultCode.REVIEWER_NOT_PERMISSION);
        }

        // 检查是否已有其他未结束的预约或借用
        boolean hasActiveLoan = canBorrowInstrument(instrument.getId());
        if (hasActiveLoan) {
            throw new BusinessException(ResultCode.INSTRUMENT_IS_RESERVED);
        }

        // 检查用户是否已经提交借用申请
        boolean userAlreadyApplied = instrumentLoanRepository.existsByUserIdAndInstrumentIdAndStatus(
                user.getId(),
                instrument.getId(),
                InstrumentLoanStatusEnum.PENDING_REVIEW
        );

        if (userAlreadyApplied) {
            throw new BusinessException(ResultCode.RESERVATION_IS_PENDING);
        }

        // 验证归还房间
        Room returnRoom = roomService.getRoom(dto.getReturnRoomId());
        if (returnRoom == null) {
            throw new BusinessException(ResultCode.ROOM_NOT_FOUND);
        }

        Long cost = instrument.getPointsCost();
        if (user.getPoints() < cost) {
            throw new BusinessException(ResultCode.USER_INSUFFICIENT_POINTS);
        }
        // 扣除积分
        userService.subtractPoint(user.getId(), cost);

        InstrumentLoan loan = new InstrumentLoan();
        loan.setUser(user);
        loan.setType(InstrumentLoanType.LIST);
        loan.setInstrument(instrument);
        loan.setReviewerId(dto.getReviewerId());
        loan.setRemark(dto.getRemark());
        loan.setStatus(InstrumentLoanStatusEnum.PENDING_REVIEW);
        loan.setBorrowTime(LocalDateTime.now());
        loan.setPoints(instrument.getPointsCost());
        loan.setCreateAt(LocalDateTime.now());
        loan.setReturnRoom(returnRoom);
        instrumentLoanRepository.save(loan);
    }

    // 借用仪器, 用户上传的仪器
    @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class)
    public void borrowInstrumentFromUp(User user, BorrowInstrumentFromUpDto dto) {
        // 积分在审核时扣除

        // 验证借用房间
        Room borrowRoom = roomService.getRoomAndDelete(dto.getBorrowRoomId());
        if (borrowRoom == null) {
            throw new BusinessException(ResultCode.ROOM_NOT_FOUND);
        }

        // 验证归还房间
        Room returnRoom = roomService.getRoomAndDelete(dto.getReturnRoomId());
        if (returnRoom == null) {
            throw new BusinessException(ResultCode.ROOM_NOT_FOUND);
        }

//        Long adminId = dto.getReviewerId();
//        Admin admin = adminRepository.findById(adminId).orElseThrow(() -> new BusinessException(ResultCode.REVIEWER_NOT_FOUND));
//        if (!adminPermissionService.hasInstrumentPermission(admin, instrument)){
//            throw new BusinessException(ResultCode.REVIEWER_NOT_PERMISSION);
//        }

        // 创建借用记录
        InstrumentLoan loan = new InstrumentLoan();
        loan.setUser(user);
        loan.setType(InstrumentLoanType.UP);
        loan.setReviewerId(dto.getReviewerId());
        loan.setRemark(dto.getRemark());
        loan.setStatus(InstrumentLoanStatusEnum.PENDING_REVIEW);
        loan.setCreateAt(LocalDateTime.now());
        loan.setInstrumentName(dto.getInstrumentName());
        loan.setInstrumentImgs(dto.getInstrumentImgs().toString());
        loan.setBorrowTime(LocalDateTime.now());
        loan.setBorrowRoom(borrowRoom);
        loan.setReturnRoom(returnRoom);
        instrumentLoanRepository.save(loan);
    }

    // 获取借还记录通过用户
    public Page<InstrumentLoan> getLoansByUser(User user, String type, Pageable pageable) {
        Page<InstrumentLoan> pages;

        if ("已归还".equals(type)) {
            pages = instrumentLoanRepository.findByUserAndStatus(user, InstrumentLoanStatusEnum.RETURNED, pageable);
        } else {
            // 非已归还的所有状态都视为已申请
            pages = instrumentLoanRepository.findByUserAndStatusNot(user, InstrumentLoanStatusEnum.RETURNED, pageable);
        }

        return pages;
    }


    // 验证仪器是否可被借用
    public boolean canBorrowInstrument(Long instrumentId) {
        // 判断是否有未归还的已通过借用记录
        boolean hasActiveLoan = instrumentLoanRepository.existsByInstrumentIdAndStatus(
                instrumentId,
                InstrumentLoanStatusEnum.APPROVED
        );

        if (hasActiveLoan) {
            return false;  // 已借出，不能借用
        }

        // 判断是否有未来（包含今天）的已通过且未取消的预约
        return reservationRepository.existsByTypeAndInstrumentIdAndStatusAndDateGreaterThanEqual(
                ReservationTypeEnum.INSTRUMENT,
                instrumentId,
                ReservationStatusEnum.APPROVED,
                LocalDate.now()
        );

    }

    // 拒绝审核
    @Transactional
    public void rejectInstrumentLoan(Long reviewerId, Long id) {
        InstrumentLoan loan = instrumentLoanRepository.findById(id).orElseThrow(() -> new BusinessException(ResultCode.INSTRUMENT_LOAN_NOT_FOUND));
        if (loan.getStatus() != InstrumentLoanStatusEnum.PENDING_REVIEW) {
            throw new BusinessException(ResultCode.INSTRUMENT_LOAN_REVIEWED);
        }
        loan.setStatus(InstrumentLoanStatusEnum.REJECTED);
        instrumentLoanRepository.save(loan);
    }

    // 通过仪器借还申请
    @Transactional
    public void approveInstrumentLoan(Long reviewerId, Long id) {
        InstrumentLoan loan = instrumentLoanRepository.findById(id).orElseThrow(() -> new BusinessException(ResultCode.INSTRUMENT_LOAN_NOT_FOUND));
        if (loan.getStatus() != InstrumentLoanStatusEnum.PENDING_REVIEW) {
            throw new BusinessException(ResultCode.INSTRUMENT_LOAN_REVIEWED);
        }
        loan.setStatus(InstrumentLoanStatusEnum.APPROVED);
        instrumentLoanRepository.save(loan);
    }

    // 获取未审核的仪器借还记录
    public Page<InstrumentLoan> getInstrumentLoansByNotReview(long reviewerId, Pageable pageable) {
        return instrumentLoanRepository.findByReviewerIdAndStatus(reviewerId, InstrumentLoanStatusEnum.PENDING_REVIEW, pageable);

    }

    public Page<InstrumentLoan> getInstrumentLoansByReviewer(long reviewerId, Pageable pageable) {
        return instrumentLoanRepository.findByReviewerId(reviewerId, pageable);
    }

    // 用户返还仪器
    public void returnInstrument(User user, ReturnInstrumentDto dto) {
        log.info("用户 {} 返还仪器 {}", user.getId(), dto.getId());
        InstrumentLoan loan = instrumentLoanRepository.findByUserAndId(user, dto.getId());
        if (loan == null) {
            throw new BusinessException(ResultCode.INSTRUMENT_LOAN_NOT_FOUND);
        }
        if (loan.getStatus() != InstrumentLoanStatusEnum.APPROVED) {
            throw new BusinessException(ResultCode.INSTRUMENT_LOAN_NOT_STATUS);
        }
        loan.setStatus(InstrumentLoanStatusEnum.RETURNED);
        loan.setReturnTime(LocalDateTime.now());
        loan.setReturnImgs(dto.getReturnImgs().toString());

        instrumentLoanRepository.save(loan);
    }

    @Transactional(rollbackFor = Exception.class)
    public void approveInstrumentLoanByUp(Long reviewerId, Long id, Long points) {
        log.info("管理员 {} 审核通过用户上传仪器借用申请 {} 扣除积分 {}", reviewerId, id, points);
        InstrumentLoan loan = instrumentLoanRepository.findById(id).orElseThrow(() -> new BusinessException(ResultCode.INSTRUMENT_LOAN_NOT_FOUND));
        if (loan.getStatus() != InstrumentLoanStatusEnum.PENDING_REVIEW) {
            throw new BusinessException(ResultCode.INSTRUMENT_LOAN_REVIEWED);
        }
        userService.subtractPoint(loan.getUser().getId(), points);
        loan.setStatus(InstrumentLoanStatusEnum.APPROVED);
        instrumentLoanRepository.save(loan);
    }

    public void deleteInstrument(Long id) {
        Instrument instrument = instrumentRepository.findByIdAndDeleteFalse(id).orElseThrow(() -> new BusinessException(ResultCode.INSTRUMENT_NOT_FOUND));
        instrument.setDelete(true);
        instrumentRepository.save(instrument);
    }
}
